/**
 * 
 */
package edu.wpi.cs509.clientlogic;

import edu.wpi.cs509.common.exceptions.ServerUnreachable;
import edu.wpi.cs509.representation.UserPair;
import edu.wpi.cs509.representation.GameID;
import edu.wpi.cs509.ui.intfc.ClientLogicRequest;
import edu.wpi.cs509.ui.intfc.GameEventNotify;
import edu.wpi.cs509.messages.MsgBc;
import edu.wpi.cs509.messages.server.*;
import edu.wpi.cs509.network.Connection;

/**
 * The ClientDownstreamLogic class implements Client -> Server logic on
 * the client logic.
 *
 * @author jadelorie
 * @author lwante
 * @author mjsanchez
 */
public class ClientDownstreamLogic implements ClientLogicRequest  {

	//Constructor is private 
	private ClientDownstreamLogic() {}
	
	private static class ClientDownstreamLogicSingleton {
		private static final ClientDownstreamLogic instance = new ClientDownstreamLogic();
	}
	
	public static ClientDownstreamLogic getInstance() {
		return ClientDownstreamLogicSingleton.instance;
	}
	
	/**
	 * This will send a message to the server.  Mainly intended to
	 * collapse the long list of .get methods needed to get the
	 * server connection.
	 * 
	 * @param msg - The message to send to the server
	 */
	public void sendDownstream(MsgBc msg) throws ServerUnreachable {
		ClientLogic.getInstance().getUserSession().getConnection().sendMessage(msg);
	}
	
	/**
	 * Login a user.  The userSession is expected to be null
	 * initially and should be set to the created userSession
	 * before sending the login request to the server
	 * 
	 * @param username - Username to login
	 * @param password - password
	 * @param host - Host to connect to
	 */
	@Override
	public void login(String username, String password, String host)
			throws ServerUnreachable {
		if (host == null) {
			throw new ServerUnreachable("Server unspecified.");
		}
		if (ClientLogic.getInstance().getUserSession() != null) {
			// TODO: Throw an exception indicating we are already logged in?
		}

		try {
			// Create a new user session
			UserSession session = new UserSession(username);
			
			// Create a network connection to the server
			Connection serverConnection = new Connection(session, host);
			
			// Add the connection to the User session and save it in the ClientLogic
			session.setConnection(serverConnection);
			ClientLogic.getInstance().setUserSession(session);
			
			// Send a login message to the server
			sendDownstream(new MsgUserLogin(new UserPair(username, password)));
			
		} catch (Exception e) {
			throw new ServerUnreachable("Could not connect to " + host);
		}
		
	}

	/**
	 * @Description: Logout a user session.
	 * 
	 * @param session - User session to logout
	 */
	@Override
	public void logout(UserSession session) {
		if (session != null)
			session.getConnection().sendMessage(new MsgLogout());
	}

	/**
	 * @Description: Request the current leaderboard based off of a certain rank and the
	 *               number of players you want to see.
	 *
	 * @param rank - Starting rank
	 * @param count - Number of samples starting from rank
	 */
	@Override
	public void requestLeaderBoard(int rank, int count) throws ServerUnreachable {
		// Make sure rank and count are non-negative. Let's also limit the amount of user's we'll get back
		if (rank < 0 || count < 0 || count > 100) {
			System.out.println("Bad rank or count supplied "+rank+" "+count);
			return;
		}
		
		try{
			// Send get leaderboard message
			sendDownstream(new MsgGetLeaderBrdRank(rank, count));
		} catch (Exception e) {
			throw new ServerUnreachable("Unable to send message to the server");
		}
	}

	/**
	 * @Description: Request the current leaderboard based off a certain username and the
	 *               number of players you want to see.
	 *
	 * @param username - Find a particular username in the leaderboard
	 * @param count - Number of samples starting from that username
	 */
	@Override
	public void requestLeaderBoard(String username, int count)
			throws ServerUnreachable {
		// Make sure rank and count are non-negative. Let's also limit the amount of user's we'll get back
		if (username == null || username.isEmpty() || count < 0 || count > 100) {
			System.out.println("Bad username or count supplied "+count+" "+username);
			return;
		}
		
		try{
			// Send get leaderboard message
			sendDownstream(new MsgGetLeaderBrdUser(username, count));
		} catch (Exception e) {
			throw new ServerUnreachable("Unable to send message to the server");
		}
	}

	/**
	 * @Description: Get stats for a particular user.
	 *
	 * @param username - Username that we want stats for
	 */
	@Override
	public void requestPersonalStats(String username) throws ServerUnreachable {
		// Probably should relay this up to the UI.
		if (username == null || username.isEmpty()) {
			System.out.println("No username supplied");
			return;
		}
		
		System.out.println("----------Requesting personal stats");
		try{
			// Send user stats message with dummy -1 for rank
			sendDownstream(new MsgGetStats(new UserPair(username), -1));
		} catch (Exception e) {
			throw new ServerUnreachable("Unable to send message to the server");
		}
	}

	/**
	 * @Description: Request a list of games.
	 */
	@Override
	public void requestGames() throws ServerUnreachable {
		try{
			// Send request joinable message
			sendDownstream(new MsgGetGames());
		} catch (Exception e) {
			throw new ServerUnreachable("Unable to send message to the server");
		}
	}

	/**
	 * @Description: Start a new game.
	 * 
	 * @param session - User session that wants to start a game 
	 * @param event - Interface method used to notify the UI of asynchronous events
	 */
	@Override
	public void startNewGame(UserSession session, GameEventNotify notify)
			throws ServerUnreachable {
		if (session == null) {
			System.out.println("Bad user session provided");
		}
		
		// Set the game event handle to talk back to the GUI
		ClientLogic.getInstance().setGameEventHandle(notify);
		
		try {
			sendDownstream(new MsgCreateGame());				
		} catch (Exception e) {
			throw new ServerUnreachable("Unable to send createGame message to the server");
		}
	}

	/**
	 * @Description: Join a new game.
	 * 
	 * @param session - User session that wants to start a game
	 * @param gameID - Game ID to join
	 * @param event - Interface method used to notify the UI of asynchronous events
	 */
	@Override
	public void joinGame(UserSession session, long gameID,
			GameEventNotify notify) throws ServerUnreachable {
		
		if (session == null) { 
			System.out.println("Bad user session or game ID provided");
		}
		
		if (gameID < 0) {
			System.out.println("Bad game ID provided");
		}
		
		// Set the game event handle to talk back to the GUI
		ClientLogic.getInstance().setGameEventHandle(notify);
		
		try {
			String game_id = "";
			
			// GameID expects a string, so convert here
			game_id = String.valueOf(gameID);
			sendDownstream(new MsgJoinGame(new GameID(game_id)));
			
		} catch (Exception e) {
			throw new ServerUnreachable("Unable to send joinGame message to the server");
		}
	}

	/**
	 * @Description: Observer a game.
	 * 
	 * @param session - User session that wants to start a game
	 * @param gameID - Game ID to join
	 * @param event - Interface method used to notify the UI of asynchronous events
	 */
	@Override
	public void spectateStart(UserSession session, long gameID,
			GameEventNotify notify) throws ServerUnreachable {
		if (session == null) { 
			System.out.println("Bad user session or game ID provided");
		}
		
		if (gameID < 0) {
			System.out.println("Bad game ID provided");
		}
		
		// Set the game event handle to talk back to the GUI
		ClientLogic.getInstance().setGameEventHandle(notify);
		
		try {
			sendDownstream(new MsgObserveGame(new GameID("",gameID)));
			
		} catch (Exception e) {
			throw new ServerUnreachable("Unable to send specateGame message to the server");
		}
	}


	/**
	 * @Description: Request the Stratego game rules.
	 */
	@Override
	public void requestStrategoRules() throws ServerUnreachable {
		// Request the stratego rules
		//sendDownstream(new MsgStrategoRules());
	}

}
